SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
3.0 KB · 57 lines typescript
Raw Blame History
1import { z } from "zod";2import { withUser, parseBody, json, ApiError } from "@/lib/api";3import { duplicateConversation, exportConversation, shareConversation, revokeShare, revokeShareById, getShare, listShares, deleteMessage, EXPORT_FORMATS } from "@/lib/conversations/service";4import { LIMITS } from "@/lib/rate-limit";5import { APP_URL } from "@/lib/env";67export const dynamic = "force-dynamic";8type P = { id: string };910const schema = z.discriminatedUnion("action", [11  z.object({ action: z.literal("duplicate"), title: z.string().max(200).optional() }),12  z.object({ action: z.literal("branch"), messageId: z.string().max(64), title: z.string().max(200).optional() }),13  /** Prefer `GET /api/conversations/[id]/export?format=` for downloads; kept for existing callers. */14  z.object({ action: z.literal("export"), format: z.enum(EXPORT_FORMATS) }),15  /** `messageIds` → share only those messages (new link). Without it → the conversation's single full link (upsert). */16  z.object({ action: z.literal("share"), messageIds: z.array(z.string().max(64)).max(500).optional() }),17  /** Revoke every active link of the conversation, or one link when `shareId` is given. */18  z.object({ action: z.literal("unshare"), shareId: z.string().max(64).optional() }),19  z.object({ action: z.literal("share-status") }),20  z.object({ action: z.literal("list-shares") }),21  z.object({ action: z.literal("delete-message"), messageId: z.string().max(64) }),22]);2324export const POST = withUser<P>(25  async ({ req, user }, { id }) => {26    const body = await parseBody(req, schema);27    switch (body.action) {28      case "duplicate":29        return json({ conversation: await duplicateConversation(user.id, id, { title: body.title }) }, { status: 201 });30      case "branch":31        return json({ conversation: await duplicateConversation(user.id, id, { uptoMessageId: body.messageId, title: body.title }) }, { status: 201 });32      case "export": {33        const out = await exportConversation(user.id, id, body.format, { appUrl: APP_URL });34        return new Response(out.body, { headers: { "Content-Type": out.contentType, "Content-Disposition": `attachment; filename="${out.filename}"` } });35      }36      case "share": {37        const res = await shareConversation(user.id, id, { messageIds: body.messageIds });38        return json({ ...res, path: `/share/${res.id}` }, { status: res.created ? 201 : 200 });39      }40      case "unshare":41        if (body.shareId) await revokeShareById(user.id, body.shareId);42        else await revokeShare(user.id, id);43        return json({ ok: true });44      case "share-status":45        return json({ share: await getShare(user.id, id), shares: await listShares(user.id, id) });46      case "list-shares":47        return json({ shares: await listShares(user.id, id) });48      case "delete-message":49        await deleteMessage(user.id, id, body.messageId);50        return json({ ok: true });51      default:52        throw new ApiError(400, "Unknown action");53    }54  },55  { limit: { ...LIMITS.share, key: "conv-actions" } },56);57